package itemMining;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map.Entry;

import thesis.DataObject;
import thesis.FSModule;
import thesis.InfoUnit;

import experiment.Dimension;
import experiment.Parameter;

public class FullyCorrelatedItem {

	private class Itemset {
		HashMap<String, Integer> items = new HashMap<String, Integer>();
		HashMap<Long, Double> objs = new HashMap();
		double support = 0;
		double corr = 0;
		int dimId = -1;

		private double calCorrelation(HashMap<String, Double> valueProb, int n) {
			double result = 0;
			if (this.support >= 2) {
				double ep = 1;
				for (String key : this.items.keySet()) {
					ep *= valueProb.get(key);
				}

				double maxp = 0;
				for (String key : this.items.keySet()) {
					if (valueProb.get(key) > maxp) {
						maxp = valueProb.get(key);
					}
				}
				double tp = this.support / n;
				result = tp / maxp;
				// result = tp / Math.sqrt(ep);
				// result = binomial(tp, this.support, n)
				// / binomial(ep, this.support, n);
			}
			this.corr = result;
			return result;
		}

		public Itemset(HashMap<String, Integer> items,
				HashMap<Long, Double> objs, double support) {
			this.items = items;
			this.objs = objs;
			this.support = support;
		}
	}

	private Itemset merge(Itemset s1, Itemset s2, ArrayList<Itemset> prevLevel,
			HashMap<String, HashMap<Long, Double>> value2obj) {
		String diff = null;
		for (String key : s2.items.keySet()) {
			if (!s1.items.containsKey(key)) {
				if (diff != null) { // more than 2 items are different
					return null;
				}
				diff = key;
			}
		}
		// check whether the itemset has uncorrelated subset
		HashSet<String> remainings = new HashSet<String>();
		for (Itemset set : prevLevel) {
			HashMap<String, Integer> temp = (HashMap<String, Integer>) s1.items
					.clone();
			temp.put(diff, -1);
			for (String key : set.items.keySet()) {
				temp.remove(key);
			}
			if (temp.size() == 1) {
				for (String key : temp.keySet()) {
					remainings.add(key);
				}
			}
		}
		if (remainings.size() == s1.items.size() + 1) {
			HashMap<Long, Double> mergedObjs = new HashMap();
			HashMap<Long, Double> objsFromDiff = value2obj.get(diff);
			double total = 0;
			for (Entry<Long, Double> obj : s1.objs.entrySet()) {
				Double prob = objsFromDiff.get(obj.getKey());
				if (prob != null) {
					total += obj.getValue() * prob;
					mergedObjs.put(obj.getKey(), obj.getValue() * prob);
				}
			}
			HashMap<String, Integer> mergeItemset = (HashMap<String, Integer>) s1.items
					.clone();
			mergeItemset.put(diff, -1);
			return new Itemset(mergeItemset, mergedObjs, total);
		}
		return null;

	}

	private double binomial(double pr, double k, int n) {
		int time = (int) (Math.round(k));
		int comb = 1;
		for (int i = 1; i <= time; i++) {
			comb = comb * (n - i + 1) / i;
		}
		return comb * Math.pow(pr, time) * Math.pow(1 - pr, n - time);
	}

	private void addItemset(ArrayList<Itemset> sets, Itemset set) {
		boolean in = false;
		for (Itemset s : sets) {
			boolean equal = true;
			for (String key : set.items.keySet()) {
				if (!s.items.containsKey(key)) {
					equal = false;
					break;
				}
			}
			if (equal) {
				return;
			}
		}
		sets.add(set);
	}

	public void convertDim(HashMap<String, HashMap<Long, Double>> value2obj,
			int size, double thresh) {
		// calculate prob of single-items and prepare level-1 itemsets
		HashMap<String, Double> valueProb = new HashMap();
		ArrayList<Itemset> l1 = new ArrayList<Itemset>();
		for (Entry<String, HashMap<Long, Double>> entry : value2obj.entrySet()) {
			double total = 0;
			for (Double prob : entry.getValue().values()) {
				total += prob;
			}
			valueProb.put(entry.getKey(), total / size);
			HashMap<String, Integer> items = new HashMap<String, Integer>();
			items.put(entry.getKey(), -1);
			l1.add(new Itemset(items, entry.getValue(), total));
		}
		// ---- start apriori -----------
		ArrayList<ArrayList<Itemset>> LC = new ArrayList();
		int count = 0;
		while (!l1.isEmpty()) {
			ArrayList<Itemset> l2 = new ArrayList<Itemset>();
			for (int i = 0; i < l1.size(); i++) {
				for (int j = i + 1; j < l1.size(); j++) {
					Itemset itemset = merge(l1.get(i), l1.get(j), l1, value2obj);
					if (itemset != null) {
						if (itemset.calCorrelation(valueProb, size) > thresh) {
							addItemset(l2, itemset);
						}
					}
				}
			}
			if (count != 0) {
				if (LC.isEmpty()) {
					LC.add(l1);
				} else {
					ArrayList<Itemset> itemsetsAfterPrune = new ArrayList();
					for (Itemset s1 : l1) {
						boolean include = false;
						for (Itemset s2 : l2) {
							boolean s1Ins2 = true;
							for (String key : s1.items.keySet()) {
								if (!s2.items.containsKey(key)) {
									s1Ins2 = false;
									break;
								}
							}
							if (s1Ins2) {
								include = true;
								break;
							}
						}
						if (!include) {
							itemsetsAfterPrune.add(s1);
						}
					}
					LC.add(itemsetsAfterPrune);
				}
			}
			l1 = l2;
			count++;
		}
		// ---- compute max correlation -----
		double maxCorr = -10000;
		for (ArrayList<Itemset> level : LC) {
			for (Itemset itemset : level) {
				if (itemset.corr > maxCorr) {
					maxCorr = itemset.corr;
				}
			}
		}
		// ---- update value2obj -----
		for (ArrayList<Itemset> level : LC) {
			for (Itemset itemset : level) {
				if (itemset.corr > maxCorr * Parameter.corrPercentage) {
					StringBuilder key = new StringBuilder();
					for (String str : itemset.items.keySet()) {
						key.append(str + "#");
					}
					System.out.print(key.toString() + " ");
					value2obj.put(key.toString(), itemset.objs);
				}
			}
			System.out.println();
		}

	}

	private Itemset mergeCrossAttr(Itemset s1, Itemset s2,
			ArrayList<Itemset> prevLevel, List<Dimension> dims) {
		String diff = null;
		int diffDim = -1;
		for (Entry<String, Integer> entry : s2.items.entrySet()) {
			if (!s1.items.containsKey(entry.getKey())) {
				if (diff != null) { // more than 2 items are different
					return null;
				}
				diff = entry.getKey();
				diffDim = entry.getValue();
				for (Integer value : s1.items.values()) {
					if (diffDim == value) {
						return null;
					}
				}
			}
		}
		// check whether the itemset has uncorrelated subset
		HashSet<String> remainings = new HashSet<String>();
		for (Itemset set : prevLevel) {
			HashMap<String, Integer> temp = (HashMap<String, Integer>) s1.items
					.clone();
			temp.put(diff, -1);
			for (String key : set.items.keySet()) {
				temp.remove(key);
			}
			if (temp.size() == 1) {
				for (String key : temp.keySet()) {
					remainings.add(key);
				}
			}
		}
		if (remainings.size() == s1.items.size() + 1) {
			HashMap<Long, Double> mergedObjs = new HashMap();
			HashMap<Long, Double> objsFromDiff = dims.get(diffDim).value2obj
					.get(diff);
			double total = 0;
			for (Entry<Long, Double> obj : s1.objs.entrySet()) {
				Double prob = objsFromDiff.get(obj.getKey());
				if (prob != null) {
					total += obj.getValue() * prob;
					mergedObjs.put(obj.getKey(), obj.getValue() * prob);
				}
			}
			HashMap<String, Integer> mergeItemset = (HashMap<String, Integer>) s1.items
					.clone();
			mergeItemset.put(diff, diffDim);
			return new Itemset(mergeItemset, mergedObjs, total);
		}
		return null;

	}

	public double[][] marginal2Joint(int dimensionLimit,
			final List<Dimension> dimensions,
			final HashMap<Long, DataObject> objects, ArrayList<InfoUnit> units,
			double thresh) {
		// calculate prob of single-items and prepare level-1 itemsets
		HashMap<String, Double> valueProb = new HashMap();
		ArrayList<Itemset> l1 = new ArrayList<Itemset>();
		int dimCount = 0;
		for (Dimension dim : dimensions) {
			for (Entry<String, HashMap<Long, Double>> entry : dim.value2obj
					.entrySet()) {
				double total = 0;
				for (Double prob : entry.getValue().values()) {
					total += prob;
				}
				valueProb.put(entry.getKey(), total / objects.size());
				HashMap<String, Integer> items = new HashMap<String, Integer>();
				items.put(entry.getKey(), dimCount);
				l1.add(new Itemset(items, entry.getValue(), total));
			}
			dimCount++;
		}
		// ---- start apriori -----------
		ArrayList<ArrayList<Itemset>> LC = new ArrayList();
		int count = 0;
		while (!l1.isEmpty()) {
			ArrayList<Itemset> l2 = new ArrayList<Itemset>();
			for (int i = 0; i < l1.size(); i++) {
				for (int j = i + 1; j < l1.size(); j++) {
					Itemset itemset = mergeCrossAttr(l1.get(i), l1.get(j), l1,
							dimensions);
					if (itemset != null) {
						if (itemset.calCorrelation(valueProb, objects.size()) > thresh) {
							addItemset(l2, itemset);
						}
					}
				}
			}
				if (LC.isEmpty()) {
					LC.add(l1);
				} else {
					ArrayList<Itemset> itemsetsAfterPrune = new ArrayList();
					for (Itemset s1 : l1) {
						boolean include = false;
						for (Itemset s2 : l2) {
							boolean s1Ins2 = true;
							for (String key : s1.items.keySet()) {
								if (!s2.items.containsKey(key)) {
									s1Ins2 = false;
									break;
								}
							}
							if (s1Ins2) {
								include = true;
								break;
							}
						}
						if (!include) {
							itemsetsAfterPrune.add(s1);
						}
					}
					LC.add(itemsetsAfterPrune);
				}
			
			l1 = l2;
			count++;
		}
		// ---- compute max correlation -----
		double maxCorr = -10000;
		for (ArrayList<Itemset> level : LC) {
			for (Itemset itemset : level) {
				if (itemset.corr > maxCorr) {
					maxCorr = itemset.corr;
				}
			}
		}
		// ---- update value2obj -----
		ArrayList<Itemset> allItems = new ArrayList();
		int inter_corr = 0;
		for (ArrayList<Itemset> level : LC) {
			for (Itemset itemset : level) {
				if (itemset.corr > maxCorr * Parameter.corrPercentage
						|| itemset.items.size() == 1) {
					allItems.add(itemset);
					if (itemset.items.size() > 1) {
						inter_corr++;
					}
				}
			}
		}
		System.out.println("Number of inter_corr: " + inter_corr);
		double[][] probs = new double[objects.size()][allItems.size()];
		HashMap<Long, Integer> db2internal = new HashMap();
		for (DataObject obj : objects.values()) {
			db2internal.put(obj.getDbId(), obj.getInternId());
		}
		for (int i = 0; i < allItems.size(); i++) {
			Itemset set = allItems.get(i);
			InfoUnit u = new InfoUnit(i, set.items.size());
			units.add(u);
			for (Entry<Long, Double> entry : set.objs.entrySet()) {
				probs[db2internal.get(entry.getKey())][i] = entry.getValue();
			}
			StringBuilder key = new StringBuilder();
			for (String str : set.items.keySet()) {
				key.append(str + "*");
			}
			u.setName(key.toString());
			System.out.println(key.toString() + " ");
		}
		return probs;
	}
}
